package REMC.monteCarlo;

import java.io.FileWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import main.Configuration;
import mutation.MutationManager;
import REMC.objects.Replica;

/**
 * @author Tal Cohen & Adiel Ashrov
 *this class represent the monte carlo algorithm
 */
public class MonteCarloSearch {

	private MutationManager mutationManager;
	private FileWriter fileWriter; // for getting data for a single monte carlo search
	private Map<Integer, Integer> countOccur;
	private Integer monteCarloLoopSize; 
	private Double emitLastFraction;
	private boolean isSaveStatistics;

	public MonteCarloSearch(Configuration config, boolean isSaveStatistics){

		monteCarloLoopSize = config.monteCarloLoopSize;
		mutationManager = new MutationManager(config);

		this.isSaveStatistics = isSaveStatistics; //enables or disables the collect of statistics
		if(isSaveStatistics){ 
			countOccur = new HashMap<Integer, Integer>();
			emitLastFraction = config.emitLastFraction;
		}
		else{ //if we don't! need statistics there is no point in initializing this members
			countOccur = null;
			emitLastFraction = 0.0; //not saving any data
		}

	}

	/**
	 * performing the monte carlo algorithm itself!
	 * @param replica to perform algorithm on
	 * @return the replica after the search
	 */
	public Replica search(Replica replica){
		double q, metropolisProb,exponent;
		Replica repAfterMutate;
		boolean skipCount = false;

		for (int i = 0; i < monteCarloLoopSize; i++) {
			repAfterMutate = new Replica(replica);
			//performing mutation on replica (not changing the replica, outcome is in repAfterMutate)
			this.mutationManager.mutate(replica,repAfterMutate,100);
			double energyDiff = repAfterMutate.getEnergy() - replica.getEnergy();
			if ( energyDiff <= 0 ){
				replica = repAfterMutate;
			}else{
				q = Math.random();
				exponent = (-energyDiff)/replica.get_temperature();
				metropolisProb = Math.pow(Math.E, exponent);
				if (q < metropolisProb){
					int sizeOfConformation = repAfterMutate.conformation.size();
					//to make sure mutate succeeded in performing mutation
					if(sizeOfConformation != 0){
						replica = repAfterMutate;
						skipCount = true;
					}
				}
			}
			//collecting statistics if needed
			if (isSaveStatistics){
				if(i >= (monteCarloLoopSize*(1-emitLastFraction)) && !skipCount){
					//adding fitness to HashMap
					Integer fitness = (int) replica.getFitness();
					if (countOccur.containsKey(fitness)){
						countOccur.put(fitness, countOccur.get(fitness)+1);
					}else{
						countOccur.put(fitness, 1);
					}
				}
			}
			skipCount = false;

			//printing replica at the end of each iteration to see the progress
			//System.out.println("*************** i="+i+" ***************");
			//System.out.println(replica);
		}
		//System.out.println("Finished Monte Carlo Search with\n" + replica);
		return replica;
	}

	public void writeResultsToFile() {
		//for getting data for a single monte carlo search
		if(isSaveStatistics && fileWriter != null){
			try {
				for (Iterator<Integer> iterator = countOccur.keySet().iterator(); iterator
				.hasNext();) {
					Integer fitness = (Integer) iterator.next();
					fileWriter.write(fitness+","+countOccur.get(fitness)+"\n");
				}

				fileWriter.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}

	/**
	 * opening results file to write and deleting previous results 
	 * @param index determines the file name.
	 */
	public void init(int index) {
		if(isSaveStatistics){
			try {
				fileWriter = new FileWriter("test" + index + ".csv", false);
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			countOccur.clear();
		}
	}


}
